package com.zch.rpc.core.invoke;

import com.zch.rpc.core.annotation.FeignService;
import com.zch.rpc.core.annotation.RpcReference;
import com.zch.rpc.core.exception.RpcException;
import com.zch.rpc.core.model.FeignServiceMethodInfo;
import com.zch.rpc.core.provider.RpcProviderFactory;
import com.zch.rpc.core.serialize.Serializer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Auther: zch
 * @Date: 2019/1/5 19:58
 * @Description:
 */
public class SpringRpcInvokerFactory extends InstantiationAwareBeanPostProcessorAdapter implements ApplicationContextAware {
    private Logger logger= LoggerFactory.getLogger(SpringRpcInvokerFactory.class);
    private String ipPort;
    private ApplicationContext context;
    private String protocol;
    private static ConcurrentHashMap<String,FeignServiceMethodInfo> initMapping=new ConcurrentHashMap<>();
    @Override
    public boolean postProcessAfterInstantiation(final Object bean, final String beanName) throws BeansException{
        // collection
        final Set<String> serviceKeyList = new HashSet<>();

        // parse XxlRpcReferenceBean
        ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                if (field.isAnnotationPresent(RpcReference.class)) {//其实就是判断属性上有没有这个注解，也可以 这样判断  field.getAnnotation(XxlRpcReference.class)!=null

                    // valid
                    Class iface = field.getType();
                    if (!iface.isInterface()) {
                        throw new RpcException("reference(RpcReference) must be interface.");
                    }
                    field.setAccessible(true);//设置私有属性可以访问
                    RpcReference rpcReference = field.getAnnotation(RpcReference.class);
                    RpcReferenceBean rpcReferenceBean=new RpcReferenceBean(iface,Serializer.SerializeEnum.HESSIAN.getSerializer(),ipPort,null,rpcReference.version(),rpcReference.groupId(),protocol);

                    Object serviceProxy=rpcReferenceBean.getReference();

                    // set bean
                    field.setAccessible(true);
                    field.set(bean, serviceProxy);//给属性赋值为代理对象的值

                    logger.info(">>>>>>>>>>> invoker factory init reference bean success. serviceKey = {}, bean.field = {}.{}",
                            RpcProviderFactory.makeServiceKey(iface.getName(),rpcReference.groupId(), rpcReference.version()), beanName, field.getName());

                    // collection
                    String serviceKey = RpcProviderFactory.makeServiceKey(iface.getName(),rpcReference.groupId(), rpcReference.version());
                    serviceKeyList.add(serviceKey);

                }
            }
        });
        return super.postProcessAfterInstantiation(bean, beanName);
    }

    public String getIpPort() {
        return ipPort;
    }

    public void setIpPort(String ipPort) {
        this.ipPort = ipPort;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.context=applicationContext;
        Map<String, Object> map = context.getBeansWithAnnotation(FeignService.class);
        if (!CollectionUtils.isEmpty(map)) {
            map.forEach((k, v) -> {
                Method[] methods = v.getClass().getMethods();
                FeignService feignService = v.getClass().getAnnotation(FeignService.class);
                if (StringUtils.isEmpty(feignService.name())) {
                    throw new RpcException(v.getClass().getName() +"feignService  name can not be empty");
                }
                String serverName = feignService.name();
                for (Method method : methods) {
                    FeignService feignServiceMethod = method.getAnnotation(FeignService.class);
                    if (feignService != null) {
                        String path = feignServiceMethod.path();
                        if (!StringUtils.isEmpty(path)) {
                            String methodInfo=v.getClass().getName()+"."+method.getName();
                            putMethodInfo(methodInfo,serverName,path);
                        }
                    }
                }
            });
        }
    }

    private static void putMethodInfo(String methodInfo,String serverName,String value) {
        FeignServiceMethodInfo feignServiceMethodInfo =new FeignServiceMethodInfo();
        feignServiceMethodInfo.setServerName(serverName);
        feignServiceMethodInfo.setMappingUrl(value);
        initMapping.put(methodInfo, feignServiceMethodInfo);
    }

    public static FeignServiceMethodInfo getMethodInfo(String methodInfo) {
        return initMapping.get(methodInfo);
    }

    public String getProtocol() {
        return protocol;
    }

    public void setProtocol(String protocol) {
        this.protocol = protocol;
    }
}
